////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////

#include "Samples.hpp"
#include "IOFileException.hpp"
#include "OutOfBoundsException.hpp"
#include "Tokeniser.hpp"
#include "Convert.hpp"
#include "Statistics.hpp"
#include <iostream>
#include <sstream>
#include <fstream>

////////////////////////////////////////////////////////////////////////////////
// Namespaces
////////////////////////////////////////////////////////////////////////////////

namespace Toolkit {

////////////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////////////

using namespace std;
using namespace Exceptions;

////////////////////////////////////////////////////////////////////////////////
// Samples
////////////////////////////////////////////////////////////////////////////////

SamplesHandler::SamplesHandler(const string & filename) {
	this->Clear();
	if (!filename.empty()) this->LoadFromFile(filename);
}

SamplesHandler::SamplesHandler(const SamplesHandler & inst)
:	samples_data(inst.samples_data), data_size(inst.data_size), data_type(inst.data_type)
{}

SamplesHandler & SamplesHandler::operator = (const SamplesHandler & inst) {
	if (this != &inst) {
		this->samples_data		= inst.samples_data;
		this->data_size			= inst.data_size;
		this->data_type			= inst.data_type;
	}
	return *this;
}

void SamplesHandler::LoadFromFile(const string & filename) {
	// Init result
	vector< vector<string> > new_samples;
	// Create input stream
	ifstream file(filename.c_str());
	try {
		// Check file is open
		if (!file.is_open())
			throw IOFileException("Sample file \"" + filename + "\" doesn't exist");
		// Tokeniser
		Tokeniser tokeniser(file);
		// Set up rules
		tokeniser.AddSkipSymbol(" ");
		tokeniser.AddSkipSymbol("\t");
		tokeniser.AddSkipSymbol("\n");
		tokeniser.AddCommentLineSymbol("//");
		tokeniser.AddCommentBlockSymbol(pair<string,string>("/*","*/"));
		tokeniser.AddStringSymbol("\"");
		// Read samples
		int nb_samples = 0, new_data_size = 0;
		string new_data_type;
		bool nb_samples_ok = false, type_ok = false, size_ok = false, data_ok = false, shift_ok = false, divide_ok = false;
		vector<double> shift, divide;
		while (!nb_samples_ok || !type_ok || !size_ok || !data_ok || !file.eof()) {
			string token = tokeniser.GetNextToken(false);
			if (token == "nb_samples") { 
				if (nb_samples_ok) throw IOFileException("The number of samples is defined more than once");
				token = tokeniser.GetNextToken(true);
				nb_samples = StrToInt(token);
				if (nb_samples <= 0) throw IOFileException("The number of samples must be positive");
				nb_samples_ok = true;
			}
			else if (token == "type") {
				if (type_ok) throw IOFileException("The data type is defined more than once");
				new_data_type = tokeniser.GetNextToken(true);
				if ((new_data_type != "integer") && (new_data_type != "float") && (new_data_type != "string") && (new_data_type != "file"))
					throw IOFileException("The data type must be one of : integer, float, string, file");
				type_ok = true;
			}
			else if (token == "size") {
				if (size_ok) throw IOFileException("The data sample size is defined more than once");
				token = tokeniser.GetNextToken(true);
				new_data_size = StrToInt(token);
				if (new_data_size <= 0) throw IOFileException("The data sample size must be positive");
				size_ok = true;
			}
			else if (token == "shift") {
				if (!size_ok)
					throw IOFileException("Size should be defined before providing the shift data");
				if ((new_data_type != "integer") && (new_data_type != "float"))
					throw IOFileException("Shifting can only be done on integers or floats, current type is " + new_data_type);
				if (shift_ok) throw IOFileException("The data shift values are defined more than once");
				for (int i=0; i<new_data_size; ++i) {
					stringstream ss; double d;
					ss << tokeniser.GetNextToken(true);
					ss >> d; shift.push_back(d);
				}
				shift_ok = true;
			}
			else if (token == "divide") {
				if (!size_ok)
					throw IOFileException("Size should be defined before providing the division data");
				if ((new_data_type != "integer") && (new_data_type != "float"))
					throw IOFileException("Dividing can only be done on integers or floats, current type is " + new_data_type);
				if (divide_ok) throw IOFileException("The data division values are defined more than once");
				for (int i=0; i<new_data_size; ++i) {
					stringstream ss; double d;
					ss << tokeniser.GetNextToken(true);
					ss >> d; divide.push_back(d);
				}
				divide_ok = true;
			}
			else if (token == "data") {
				if (data_ok) throw IOFileException("More than one data section");
				if (!nb_samples_ok || !size_ok) throw IOFileException("Number of samples and sample size should be set before the data section");
				for (int i=0; i<nb_samples; ++i) {
					vector<string> sample;
					for (int j=0; j<new_data_size; ++j) {
						token = tokeniser.GetNextToken(true);
						if (token.empty())
							throw IOFileException("Missing sample data");
						if (shift_ok || divide_ok) {
							stringstream iss, oss;
							float mod_sample;
							iss << token;
							iss >> mod_sample;
							if (shift_ok) mod_sample += shift[j];
							if (divide_ok) mod_sample /= divide[j];
							oss << mod_sample;
							oss >> token;
						}
						sample.push_back(token);
					}
					new_samples.push_back(sample);
				}
				data_ok = true;
			}
			else if ((token != "") || (!file.eof()))
				throw IOFileException("\"" + token + "\" is not a correct field");
		}
		// Update
		if (this->data_type.empty())
			this->data_type = new_data_type;
		else if (this->data_type != new_data_type)
			throw IOFileException("The new data type " + new_data_type + " does not match the previous data type " + this->data_type);
		if (this->data_size == 0)
			this->data_size = new_data_size;
		else if (this->data_size != new_data_size) {
			stringstream ss;
			ss << "The new sample data size " << new_data_size << " does not match the previous data size " << this->data_size;
			throw IOFileException(ss.str());
		}
		for (unsigned int i=0; i<new_samples.size(); ++i)
			this->samples_data.push_back(new_samples[i]);
		// Close file
		if (file.is_open())	file.close();
	}
	catch (exception & e) {
		// Close file
		if (file.is_open())	file.close();
		// Throw
		throw;
	}
}

void SamplesHandler::RemoveAt(const unsigned int idx) {
	if (idx >= this->samples_data.size()) {
		stringstream ss;
		ss << "Index " << idx << " is out of bounds (size " << this->samples_data.size() << ")";
		throw OutOfBoundsException(ss.str());
	}
	vector< vector<string> >::iterator itor = this->samples_data.begin();
	for (unsigned int i=0; i<idx; ++i) ++itor;
	this->samples_data.erase(itor);
	if (this->samples_data.empty()) this->Clear();
}

void SamplesHandler::Clear() {
	this->samples_data.clear();
	this->data_size = 0;
	this->data_type = "";
}

////////////////////////////////////////////////////////////////////////////////
// SampleSet
////////////////////////////////////////////////////////////////////////////////

SampleSet::SampleSet(const string & name)
: name(name)
{}

SampleSet::SampleSet(const SampleSet & inst)
:	name(inst.name), classification(inst.classification), samples(inst.samples),
	samples_results(inst.samples_results)
{}

SampleSet::SampleSet(istream & file)
{
	this->Read(file);
}

SampleSet & SampleSet::operator = (const SampleSet & inst) {
	if (this != &inst) {
		this->name				= inst.name;
		this->classification	= inst.classification;
		this->samples 			= inst.samples;
		this->samples_results	= inst.samples_results;
	}
	return *this;
}

void SampleSet::AddSamplesFromFile(const string & filename) {
	this->samples.LoadFromFile(filename);
	while (this->samples_results.size() < this->samples.GetSamplesData().size())
		this->samples_results.push_back(vector<double>());
}

void SampleSet::ClearSamples() {
	this->samples.Clear();
	this->samples_results.clear();
}

void SampleSet::RemoveSamplesAt(unsigned int idx) {
	this->samples.RemoveAt(idx);
	vector< vector<double> >::iterator itor = this->samples_results.begin();
	for (unsigned int i=0; i<idx; ++i) ++itor;
	this->samples_results.erase(itor);
}

void SampleSet::ClearSamplesResults() {
	for (unsigned int i=0; i<this->samples_results.size(); ++i)
		this->samples_results[i].clear();
}

void SampleSet::SetSamplesResultsAt(unsigned int idx, const vector<double> & results) {
	this->samples_results[idx] = results;
}

void SampleSet::Write(ostream & file) {
	file << this->name << endl << endl;
	file << this->classification.size() << endl;
	for (unsigned int i=0; i<this->classification.size(); ++i)
		file << ((i==0)?"":" ") << classification[i];
	file << endl << endl;
	file << this->samples.data_size << endl;
	file << this->samples.data_type << endl << endl;
	file << this->samples.samples_data.size() << endl;
	for (unsigned int i=0; i<this->samples.samples_data.size(); ++i) {
		for (int j=0; j<this->samples.data_size; ++j)
			file << ((j==0)?"":" ") << this->samples.samples_data[i][j];
		file << endl;
	}
}

void SampleSet::Read(istream & file) {
	unsigned int size;
	file >> this->name;
	file >> size;
	for (unsigned int i=0; i<size; ++i) {
		double c; file >> c;
		this->classification.push_back(c);
	}
	file >> this->samples.data_size;
	file >> this->samples.data_type;
	file >> size;
	for (unsigned int i=0; i<size; ++i) {
		vector<string> sample;
		for (int j=0; j<this->samples.data_size; ++j) {
			string str; file >> str;
			sample.push_back(str);
		}
		this->samples.samples_data.push_back(sample);
	}
	this->samples_results.resize(size);
}

void SampleSet::ExportResults(ostream & file) {
	file << this->name << " = [" << endl;
	for (unsigned int i=0; i<this->samples.samples_data.size(); ++i) {
		for (unsigned int j=0; j<this->samples_results[i].size(); ++j)
			file << ((j==0)?"":" ") << this->samples_results[i][j];
		file << endl;
	}
	file << "];" << endl;
}

/*
	file << "Data size  " << this->samples.data_size << endl;
	file << "Data type  " << this->samples.data_type << endl;
	file << "Nb Samples " << this->samples.samples_data.size() << endl;
	file << "Outputs Average" << endl;
	vector< vector<double> > av_out;
	for (unsigned int i=0; i<this->samples_results.size(); ++i) {
		while (av_out.size() < this->samples_results[i].size()) av_out.push_back(vector<double>());
		for (unsigned int j=0; j<this->samples_results[i].size(); ++j)
			av_out[j].push_back(this->samples_results[i][j]);
	}
	double averages[av_out.size()];
	for (unsigned int j=0; j<av_out.size(); ++j)
		averages[j] = Average(av_out[j]);
	for (unsigned int j=0; j<av_out.size(); ++j)
		file << ((j==0)?"":" ") << averages[j];
	file << endl;
	file << "Outputs Standard deviation" << endl;
	for (unsigned int j=0; j<av_out.size(); ++j)
		file << ((j==0)?"":" ") << StdDevAve(av_out[j],averages[j]);
	file << endl;
	file << "Samples and Results" << endl;
*/

/*
for (int j=0; j<this->samples.data_size; ++j)
			file << ((j==0)?"":" ") << this->samples.samples_data[i][j];
		file << endl;
*/

////////////////////////////////////////////////////////////////////////////////
// Closing Namespaces
////////////////////////////////////////////////////////////////////////////////

}

////////////////////////////////////////////////////////////////////////////////
